/**
 * $Id: GraphTransferable.java,v 1.10 2010-01-21 14:24:00 gaudenz Exp $
 * Copyright (c) 2008, Gaudenz Alder
 */
package com.kg.rails.component.editor.swing.util;

import com.kg.rails.component.editor.util.EditorRectangle;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.plaf.UIResource;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.RenderedImage;
import java.io.*;

/**
 *
 */
public class GraphTransferable implements Transferable, UIResource,
        Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 5123819419918087664L;

    /**
     * Serialized Data Flavor. Use the following code to switch to local
     * reference flavor:
     * <code>
     * try
     * {
     * GraphTransferable.dataFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType
     * + "; class=com.kg.rails.component.editor.swing.util.GraphTransferable");
     * }
     * catch (ClassNotFoundException cnfe)
     * {
     * // do nothing
     * }
     * </code>
     * <p/>
     * If you get a class not found exception, try the following instead:
     * <code>
     * GraphTransferable.dataFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType
     * + "; class=com.kg.rails.component.editor.swing.util.GraphTransferable", null,
     * new com.kg.rails.component.editor.swing.util.GraphTransferable(null, null).getClass().getClassLoader());
     * </code>
     */
    public static DataFlavor dataFlavor;

    /**
     *
     */
    private static DataFlavor[] htmlFlavors;

    /**
     *
     */
    private static DataFlavor[] stringFlavors;

    /**
     *
     */
    private static DataFlavor[] plainFlavors;

    /**
     *
     */
    private static DataFlavor[] imageFlavors;

    /**
     *
     */
    protected Object[] cells;

    /**
     *
     */
    protected EditorRectangle bounds;

    /**
     *
     */
    protected ImageIcon image;

    /**
     *
     */
    public GraphTransferable(Object[] cells, EditorRectangle bounds) {
        this(cells, bounds, null);
    }

    /**
     *
     */
    public GraphTransferable(Object[] cells, EditorRectangle bounds,
                             ImageIcon image) {
        this.cells = cells;
        this.bounds = bounds;
        this.image = image;
    }

    /**
     * @return Returns the cells.
     */
    public Object[] getCells() {
        return cells;
    }

    /**
     * Returns the unscaled, untranslated bounding box of the cells.
     */
    public EditorRectangle getBounds() {
        return bounds;
    }

    /**
     *
     */
    public ImageIcon getImage() {
        return image;
    }

    /**
     *
     */
    public DataFlavor[] getTransferDataFlavors() {
        DataFlavor[] richerFlavors = getRicherFlavors();

        int nRicher = (richerFlavors != null) ? richerFlavors.length : 0;
        int nHtml = (isHtmlSupported()) ? htmlFlavors.length : 0;
        int nPlain = (isPlainSupported()) ? plainFlavors.length : 0;
        int nString = (isPlainSupported()) ? stringFlavors.length : 0;
        int nImage = (isImageSupported()) ? stringFlavors.length : 0;
        int nFlavors = nRicher + nHtml + nPlain + nString + nImage;

        DataFlavor[] flavors = new DataFlavor[nFlavors];

        // fill in the array
        int nDone = 0;

        if (nRicher > 0) {
            System.arraycopy(richerFlavors, 0, flavors, nDone, nRicher);
            nDone += nRicher;
        }

        if (nHtml > 0) {
            System.arraycopy(htmlFlavors, 0, flavors, nDone, nHtml);
            nDone += nHtml;
        }

        if (nPlain > 0) {
            System.arraycopy(plainFlavors, 0, flavors, nDone, nPlain);
            nDone += nPlain;
        }

        if (nString > 0) {
            System.arraycopy(stringFlavors, 0, flavors, nDone, nString);
            nDone += nString;
        }

        if (nImage > 0) {
            System.arraycopy(imageFlavors, 0, flavors, nDone, nImage);
            nDone += nImage;
        }

        return flavors;
    }

    /**
     * Some subclasses will have flavors that are more descriptive than HTML or
     * plain text. If this method returns a non-null value, it will be placed at
     * the start of the array of supported flavors.
     */
    protected DataFlavor[] getRicherFlavors() {
        return new DataFlavor[]{dataFlavor};
    }

    /**
     * Returns whether or not the specified data flavor is supported for this
     * object.
     *
     * @param flavor the requested flavor for the data
     * @return boolean indicating whether or not the data flavor is supported
     */
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        DataFlavor[] flavors = getTransferDataFlavors();

        for (int i = 0; i < flavors.length; i++) {
            if (flavors[i] != null && flavors[i].equals(flavor)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns an object which represents the data to be transferred. The class
     * of the object returned is defined by the representation class of the
     * flavor.
     *
     * @param flavor the requested flavor for the data
     * @throws IOException                if the data is no longer available in the requested
     *                                    flavor.
     * @throws UnsupportedFlavorException if the requested data flavor is not supported.
     * @see DataFlavor#getRepresentationClass
     */
    public Object getTransferData(DataFlavor flavor)
            throws UnsupportedFlavorException, IOException {
        if (isRicherFlavor(flavor)) {
            return getRicherData(flavor);
        } else if (isImageFlavor(flavor)) {
            if (image != null && image.getImage() instanceof RenderedImage) {
                if (flavor.equals(DataFlavor.imageFlavor)) {
                    return image.getImage();
                } else {
                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
                    ImageIO.write((RenderedImage) image.getImage(), "bmp",
                            stream);

                    return new ByteArrayInputStream(stream.toByteArray());
                }
            }
        } else if (isHtmlFlavor(flavor)) {
            String data = getHtmlData();
            data = (data == null) ? "" : data;

            if (String.class.equals(flavor.getRepresentationClass())) {
                return data;
            } else if (Reader.class.equals(flavor.getRepresentationClass())) {
                return new StringReader(data);
            } else if (InputStream.class.equals(flavor.getRepresentationClass())) {
                return new ByteArrayInputStream(data.getBytes());
            }
            // fall through to unsupported
        } else if (isPlainFlavor(flavor)) {
            String data = getPlainData();
            data = (data == null) ? "" : data;

            if (String.class.equals(flavor.getRepresentationClass())) {
                return data;
            } else if (Reader.class.equals(flavor.getRepresentationClass())) {
                return new StringReader(data);
            } else if (InputStream.class.equals(flavor.getRepresentationClass())) {
                return new ByteArrayInputStream(data.getBytes());
            }
            // fall through to unsupported

        } else if (isStringFlavor(flavor)) {
            String data = getPlainData();
            data = (data == null) ? "" : data;

            return data;
        }

        throw new UnsupportedFlavorException(flavor);
    }

    /**
     * @param flavor
     * @return Returns true if the given flavor is a richer flavor of this
     *         transferable.
     */
    protected boolean isRicherFlavor(DataFlavor flavor) {
        DataFlavor[] richerFlavors = getRicherFlavors();
        int nFlavors = (richerFlavors != null) ? richerFlavors.length : 0;

        for (int i = 0; i < nFlavors; i++) {
            if (richerFlavors[i].equals(flavor)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param flavor
     * @return the richer data flavor of this and the specified
     * @throws UnsupportedFlavorException
     */
    public Object getRicherData(DataFlavor flavor)
            throws UnsupportedFlavorException {
        if (flavor.equals(dataFlavor)) {
            return this;
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
    }

    /**
     * Returns whether or not the specified data flavor is an HTML flavor that
     * is supported.
     *
     * @param flavor the requested flavor for the data
     * @return boolean indicating whether or not the data flavor is supported
     */
    protected boolean isHtmlFlavor(DataFlavor flavor) {
        DataFlavor[] flavors = htmlFlavors;

        for (int i = 0; i < flavors.length; i++) {
            if (flavors[i].equals(flavor)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether the HTML flavors are offered. If so, the method getHTMLData
     * should be implemented to provide something reasonable.
     */
    protected boolean isHtmlSupported() {
        return false;
    }

    /**
     * Fetch the data in a text/html format
     */
    protected String getHtmlData() {
        return null;
    }

    /**
     * @param flavor
     * @return Returns true if the given flavor is an image flavor of this
     *         transferable.
     */
    protected boolean isImageFlavor(DataFlavor flavor) {
        int nFlavors = (imageFlavors != null) ? imageFlavors.length : 0;

        for (int i = 0; i < nFlavors; i++) {
            if (imageFlavors[i].equals(flavor)) {
                return true;
            }
        }

        return false;
    }

    /**
     *
     */
    public boolean isImageSupported() {
        return image != null;
    }

    /**
     * Returns whether or not the specified data flavor is an plain flavor that
     * is supported.
     *
     * @param flavor the requested flavor for the data
     * @return boolean indicating whether or not the data flavor is supported
     */
    protected boolean isPlainFlavor(DataFlavor flavor) {
        DataFlavor[] flavors = plainFlavors;

        for (int i = 0; i < flavors.length; i++) {
            if (flavors[i].equals(flavor)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether the plain text flavors are offered. If so, the method
     * getPlainData should be implemented to provide something reasonable.
     */
    protected boolean isPlainSupported() {
        return false;
    }

    /**
     * Fetch the data in a text/plain format.
     */
    protected String getPlainData() {
        return null;
    }

    /**
     * Returns whether or not the specified data flavor is a String flavor that
     * is supported.
     *
     * @param flavor the requested flavor for the data
     * @return boolean indicating whether or not the data flavor is supported
     */
    protected boolean isStringFlavor(DataFlavor flavor) {
        DataFlavor[] flavors = stringFlavors;

        for (int i = 0; i < flavors.length; i++) {
            if (flavors[i].equals(flavor)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Local Machine Reference Data Flavor.
     */
    static {
        try {
            htmlFlavors = new DataFlavor[3];
            htmlFlavors[0] = new DataFlavor("text/html;class=java.lang.String");
            htmlFlavors[1] = new DataFlavor("text/html;class=java.io.Reader");
            htmlFlavors[2] = new DataFlavor(
                    "text/html;charset=unicode;class=java.io.InputStream");

            plainFlavors = new DataFlavor[3];
            plainFlavors[0] = new DataFlavor(
                    "text/plain;class=java.lang.String");
            plainFlavors[1] = new DataFlavor("text/plain;class=java.io.Reader");
            plainFlavors[2] = new DataFlavor(
                    "text/plain;charset=unicode;class=java.io.InputStream");

            stringFlavors = new DataFlavor[2];
            stringFlavors[0] = new DataFlavor(
                    DataFlavor.javaJVMLocalObjectMimeType
                            + ";class=java.lang.String");
            stringFlavors[1] = DataFlavor.stringFlavor;

            imageFlavors = new DataFlavor[2];
            imageFlavors[0] = DataFlavor.imageFlavor;
            imageFlavors[1] = new DataFlavor("image/bmp");
        } catch (ClassNotFoundException cle) {
            System.err
                    .println("error initializing javax.swing.plaf.basic.BasicTranserable");
        }

        try {
            dataFlavor = new DataFlavor(DataFlavor.javaSerializedObjectMimeType
                    + "; class=com.kg.rails.component.editor.swing.util.GraphTransferable");
        } catch (ClassNotFoundException cnfe) {
            // do nothing
        }
    }

}
